package com.jneagle.xlstool.bzwgjmx.impl.handler;

import com.dwarfeng.subgrade.sdk.interceptor.analyse.BehaviorAnalyse;
import com.dwarfeng.subgrade.stack.exception.HandlerException;
import com.jneagle.xlstool.bzwgjmx.stack.bean.entity.StructuredData;
import com.jneagle.xlstool.bzwgjmx.stack.bean.entity.StructuredErrorInfo;
import com.jneagle.xlstool.bzwgjmx.stack.exception.WrongPasswordException;
import com.jneagle.xlstool.bzwgjmx.stack.handler.StructuredDataLoadHandler;
import com.jneagle.xlstool.bzwgjmx.stack.service.*;
import com.jneagle.xlstool.bzwgjmx.stack.structure.ProgressStatus;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFDataFormatter;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

@Component
public class StructuredDataLoadHandlerImpl extends AbstractProgressHandler implements StructuredDataLoadHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(StructuredDataLoadHandlerImpl.class);

    private final StructuredDataMaintainService structuredDataMaintainService;
    private final StructuredErrorInfoMaintainService structuredErrorInfoMaintainService;
    private final RawPartMaintainService rawPartMaintainService;
    private final RawPartDataMaintainService rawPartDataMaintainService;
    private final RawErrorInfoMaintainService rawErrorInfoMaintainService;
    private final ExportDataMaintainService exportDataMaintainService;
    private final ExportErrorInfoMaintainService exportErrorInfoMaintainService;

    @Value("${structured_data.policy.project_no_filter}")
    private String projectNoFilter;
    @Value("${structured_data.policy.project_no_filter_enabled}")
    private boolean projectNoFilterEnabled;
    @Value("${structured_data.sheet.index}")
    private int sheetIndex;
    @Value("${structured_data.row.first_data_index}")
    private int rowFirstDataIndex;
    @Value("${structured_data.column.project_no_index}")
    private int columnProjectNoIndex;
    @Value("${structured_data.column.part_no_index}")
    private int columnPartNoIndex;
    @Value("${structured_data.column.classification_name_index}")
    private int columnClassificationNameIndex;
    @Value("${structured_data.column.production_name_index}")
    private int columnProductionNameIndex;
    @Value("${structured_data.column.specification_index}")
    private int columnSpecificationIndex;
    @Value("${structured_data.column.demand_index}")
    private int columnDemandIndex;
    @Value("${structured_data.column.remark_index}")
    private int columnRemarkIndex;

    public StructuredDataLoadHandlerImpl(
            StructuredDataMaintainService structuredDataMaintainService,
            StructuredErrorInfoMaintainService structuredErrorInfoMaintainService,
            RawPartMaintainService rawPartMaintainService,
            RawPartDataMaintainService rawPartDataMaintainService,
            RawErrorInfoMaintainService rawErrorInfoMaintainService,
            ExportDataMaintainService exportDataMaintainService,
            ExportErrorInfoMaintainService exportErrorInfoMaintainService
    ) {
        this.structuredDataMaintainService = structuredDataMaintainService;
        this.structuredErrorInfoMaintainService = structuredErrorInfoMaintainService;
        this.rawPartMaintainService = rawPartMaintainService;
        this.rawPartDataMaintainService = rawPartDataMaintainService;
        this.rawErrorInfoMaintainService = rawErrorInfoMaintainService;
        this.exportDataMaintainService = exportDataMaintainService;
        this.exportErrorInfoMaintainService = exportErrorInfoMaintainService;
    }

    @Override
    @BehaviorAnalyse
    public void load(File file, String password) throws HandlerException {
        try {
            // 广播进度变更事件。
            fireProgressChanged(ProgressStatus.UNCERTAIN);

            // 清空相关实体的维护服务。
            structuredDataMaintainService.clear();
            structuredErrorInfoMaintainService.clear();
            rawPartMaintainService.clear();
            rawPartDataMaintainService.clear();
            rawErrorInfoMaintainService.clear();
            exportDataMaintainService.clear();
            exportErrorInfoMaintainService.clear();

            // 加载密码。
            org.apache.poi.hssf.record.crypto.Biff8EncryptionKey.setCurrentUserPassword(
                    StringUtils.isEmpty(password) ? null : password
            );
            // 调用 poi 对 excel 进行操作。
            try (
                    InputStream in = Files.newInputStream(file.toPath());
                    Workbook workbook = new HSSFWorkbook(in)
            ) {
                loadWorkbook(workbook);
            }
        } catch (org.apache.poi.EncryptedDocumentException e) {
            throw new WrongPasswordException(e);
        } catch (HandlerException e) {
            throw e;
        } catch (Exception e) {
            throw new HandlerException(e);
        } finally {
            // 广播进度变更事件。
            fireProgressChanged(ProgressStatus.IDLE);
        }
    }

    private void loadWorkbook(Workbook workbook) throws Exception {
        // 获取工作簿。
        Sheet sheet = workbook.getSheetAt(sheetIndex);

        // 解析行数，并确定循环的总次数。
        int currentRowIndex = rowFirstDataIndex;
        int totalRowIndex = sheet.getLastRowNum();

        // 定义数据列表。
        List<StructuredData> structuredDataList = new ArrayList<>();
        List<StructuredErrorInfo> structuredErrorInfoList = new ArrayList<>();

        // 对工作簿的每一行进行遍历，读取数据。
        for (; currentRowIndex <= totalRowIndex; currentRowIndex++) {
            fireProgressChanged(currentRowIndex - rowFirstDataIndex, totalRowIndex - rowFirstDataIndex);
            loadRow(currentRowIndex, sheet.getRow(currentRowIndex), structuredDataList, structuredErrorInfoList);
        }

        // 将数据列表中的数据推送到数据维护服务中。
        structuredDataMaintainService.batchInsert(structuredDataList);
        structuredErrorInfoMaintainService.batchInsert(structuredErrorInfoList);
    }

    private void loadRow(
            int rowIndex,
            Row row,
            List<StructuredData> structuredDataList,
            List<StructuredErrorInfo> structuredErrorInfoList
    ) {
        // 定义通用变量。
        Cell currentCell;
        DataContext currentDataContext;

        try {
            // 解析项目号。
            currentCell = makeCell(row, columnProjectNoIndex);
            currentDataContext = getDataContextAt(currentCell, RowLoadingPolicy.NORMAL);
            String projectNo = currentDataContext.getValue(DataType.STRING, String.class);
            // 如果过滤机制启用，且项目号未通过过滤。
            if (projectNoFilterEnabled && !projectNo.matches(projectNoFilter)) {
                // 日志输出。
                LOGGER.warn(
                        "第 {} 行(对应数据表是第 {} 行)的数据项目号 {} 格式不正确，将会忽略该条数据的读取",
                        rowIndex, rowIndex + 1, projectNo
                );
                // 创建失败信息，将失败信息添加到 structuredErrorInfoList 中。
                StructuredErrorInfo errorInfo = new StructuredErrorInfo(null, rowIndex, "ui.label.016");
                structuredErrorInfoList.add(errorInfo);
                // 返回，停止后续工作。
                return;
            }

            // 部件号
            currentCell = makeCell(row, columnPartNoIndex);
            currentDataContext = getDataContextAt(currentCell, RowLoadingPolicy.NORMAL);
            String partNo = currentDataContext.getValue(DataType.STRING, String.class);

            // 类别名称
            currentCell = makeCell(row, columnClassificationNameIndex);
            currentDataContext = getDataContextAt(currentCell, RowLoadingPolicy.NORMAL);
            String classificationName = currentDataContext.getValue(DataType.STRING, String.class);

            // 产品名称
            currentCell = makeCell(row, columnProductionNameIndex);
            currentDataContext = getDataContextAt(currentCell, RowLoadingPolicy.NORMAL);
            String productionName = currentDataContext.getValue(DataType.STRING, String.class);

            // 规格
            currentCell = makeCell(row, columnSpecificationIndex);
            currentDataContext = getDataContextAt(currentCell, RowLoadingPolicy.NUM2STRING);
            String specification = currentDataContext.getValue(DataType.STRING, String.class);

            // 单需
            currentCell = makeCell(row, columnDemandIndex);
            currentDataContext = getDataContextAt(currentCell, RowLoadingPolicy.NUM2STRING);
            String demand = currentDataContext.getValue(DataType.STRING, String.class);

            // 备注
            currentCell = makeCell(row, columnRemarkIndex);
            currentDataContext = getDataContextAt(currentCell, RowLoadingPolicy.NORMAL);
            String remark = currentDataContext.getValue(DataType.STRING, String.class);

            // 生成结构化数据，将结构化数据追加到列表中。
            StructuredData structuredData = new StructuredData(
                    null, projectNo, partNo, classificationName, productionName, specification, demand, remark,
                    rowIndex + 1
            );
            structuredDataList.add(structuredData);
        } catch (Exception e) {
            LOGGER.warn(
                    "读取数据表的第 " + rowIndex + " 行(对应数据表是第 "
                            + (rowIndex + 1) + " 行)数据时出现异常，异常信息为: ", e
            );
            StructuredErrorInfo errorInfo = new StructuredErrorInfo(null, rowIndex, "ui.label.017");
            structuredErrorInfoList.add(errorInfo);
        }
    }

    private Cell makeCell(Row row, int index) {
        return Optional.ofNullable(row.getCell(index)).orElse(row.createCell(index));
    }

    private DataContext getDataContextAt(Cell cell, RowLoadingPolicy policy) {
        switch (policy) {
            case NUM2STRING:
                if (Objects.equals(cell.getCellType(), CellType.NUMERIC)) {
                    String cellValue = new HSSFDataFormatter().formatCellValue(cell);
                    cell.setCellValue(cellValue);
                }
                break;
            case NORMAL:
                break;
            default:
                throw new IllegalStateException(String.format("Illegal row loading policy: %s.", policy));
        }
        return new DataContext(cell);
    }

    /**
     * 数据读取策略。
     */
    private enum RowLoadingPolicy {
        NORMAL, NUM2STRING
    }

    /**
     * 数据类型。
     */
    private enum DataType {

        STRING, NUMBER, FORMULA,
    }

    private static class DataContext {

        private final Cell cell;

        public DataContext(Cell cell) {
            this.cell = cell;
        }

        public Object getValue(DataType dataType) {
            switch (dataType) {
                case FORMULA:
                    return cell.getCellFormula();
                case NUMBER:
                    return cell.getNumericCellValue();
                case STRING:
                    return cell.getStringCellValue();
                default:
                    throw new IllegalStateException(String.format("Illegal data type: %s.", dataType));
            }
        }

        public <T> T getValue(DataType dataType, Class<T> clazz) {
            return clazz.cast(getValue(dataType));
        }

        public Cell getContextCell() {
            return cell;
        }
    }
}
